package com.pan.insist.config;

import com.pan.insist.constant.CommonConstant;
import com.pan.insist.shiro.KickOutSessionControlFilter;
import com.pan.insist.shiro.UserRealm;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import javax.servlet.Filter;
import java.util.*;

/**
 * 配置shiro权限拦截，登录
 * @author kaiji
 */
@Log4j2
@Configuration
public class ShiroConfig {

    @Value("${spring.redis.host:127.0.0.1}")
    private String host;
    @Value("${spring.redis.port:6379}")
    private String port;

    /** step: 创建拦截规则 **/
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理对象
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录地址
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 无权限跳转地址
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
        /*shiroFilterFactoryBean.setSuccessUrl("");*/

        // 自定义拦截器
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        //限制同一帐号同时在线的个数
        filtersMap.put("kickOut", kickOutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);


        // 设置权限拦截
        Map<String, String> filterChainDefinitionMap = new HashMap<>(CommonConstant.DEFAULT_INITIAL_CAPACITY);
        // 设置“记住我” 之后就可以不用等了的页面
        filterChainDefinitionMap.put("/", "user");
        filterChainDefinitionMap.put("/login", "kickOut,anon");
        filterChainDefinitionMap.put("/toLogin", "anon");
        filterChainDefinitionMap.put("/getAuthCode", "anon");
        filterChainDefinitionMap.put("/upload/**", "anon");
        filterChainDefinitionMap.put("/socket/**", "anon");
        filterChainDefinitionMap.put("/rest/**", "anon");
        // 静态资源
        filterChainDefinitionMap.put("/static/**", "anon");

        // 拦截所有权限
        filterChainDefinitionMap.put("/**", "kickOut,authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        log.info("shiro拦截器工厂类注入成功！");
        return  shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 权限验证实现类
        securityManager.setRealm(userRealm());
        // 记住我
        securityManager.setRememberMeManager(rememberMeManager());
        // 开启内存缓存
        /*securityManager.setCacheManager(new MemoryConstrainedCacheManager());*/
        // 开启redis缓存
        securityManager.setCacheManager(redisCacheManager());
        // 将session放入redis中
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     * 身份验证
     */
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    /**
     * 开启aop注解支持
     * 即在controller中使用 @RequiresPermissions("user/userList")
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        attributeSourceAdvisor.setSecurityManager(securityManager);
        return attributeSourceAdvisor;
    }

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAap = new DefaultAdvisorAutoProxyCreator();
        defaultAap.setProxyTargetClass(true);
        return defaultAap;
    }

    /**
     * redis 缓存管理
     */
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(myRedisManager());
        redisCacheManager.setPrincipalIdFieldName("username");
        // 30天过期时间
        redisCacheManager.setExpire(60*60*24*3);
        return redisCacheManager;
    }

    /** 创建一个默认的Redis对象 */
    @Bean
    public RedisManager myRedisManager() {
    	RedisManager redisManager = new RedisManager();
    	log.info("host:{}, port:{}", host, port);
    	redisManager.setHost(host+":"+port);
    	redisManager.setPassword(null);
        return redisManager;
    }

    /** 记住我 */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // 设置cookie的加密key
        byte[] decodeStr = "4AvVhmFLUs0KTA3Kprsdag==".getBytes();
        cookieRememberMeManager.setCipherKey(Base64.getDecoder().decode(decodeStr));
        return cookieRememberMeManager;
    }
    @Bean
    public SimpleCookie rememberMeCookie(){
        //remeberMe 对应前端页面的checkbox上的name
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        /* httpOnly = true：
            1. 只能通过http访问，javascript无法访问
            2. 防止xss读取cookie
        */
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(60*60*24*30);
        return simpleCookie;
    }

    /**
     * 限制同一账号登录同时登录人数控制
     * @return 过滤器
     */
    @Bean
    public KickOutSessionControlFilter kickOutSessionControlFilter() {
        KickOutSessionControlFilter kickOutSessionControlFilter = new KickOutSessionControlFilter();
        //使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
        //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
        //也可以重新另写一个，重新配置缓存时间之类的自定义缓存属性
        kickOutSessionControlFilter.setCacheManager(redisCacheManager());
        //用于根据会话ID，获取会话进行踢出操作的；
        kickOutSessionControlFilter.setSessionManager(sessionManager());
        //是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；踢出顺序。
        kickOutSessionControlFilter.setKickOutAfter(false);
        //同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
        kickOutSessionControlFilter.setMaxSession(CommonConstant.MAGIC_VALUE_ONE);
        //被踢出后重定向到的地址；
        kickOutSessionControlFilter.setKickOutUrl("/login");
        return kickOutSessionControlFilter;
    }




    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDao());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    @Bean
    public RedisSessionDAO redisSessionDao() {
        RedisSessionDAO redisSessionDao = new RedisSessionDAO();
        redisSessionDao.setRedisManager(myRedisManager());
        return redisSessionDao;
    }

    /**
     * 解决： 无权限页面不跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized") 无效
     * shiro的源代码ShiroFilterFactoryBean.Java定义的filter必须满足filter instanceof AuthorizationFilter，
     * 只有perms，roles，ssl，rest，port才是属于AuthorizationFilter，而anon，authcBasic，auchc，user是AuthenticationFilter，
     * 所以unauthorizedUrl设置后页面不跳转 Shiro注解模式下，登录失败与没有权限都是通过抛出异常。
     * 并且默认并没有去处理或者捕获这些异常。在SpringMVC下需要配置捕获相应异常来通知用户信息
     */
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver simpleMappingExceptionResolver=new SimpleMappingExceptionResolver();
        Properties properties=new Properties();
        //这里的 /notRole 是页面，不是访问的路径
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/notRole");
        properties.setProperty("org.apache.shiro.authz.UnauthenticatedException","/notRole");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }

}
